package com.maple.visit;

import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.expr.*;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import com.alibaba.druid.util.StringUtils;
import com.maple.config.ColumnInfo;
import com.maple.config.TableInfo;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 查询语句字段追加
 *
 * @author maple
 * @version 1.0
 * @since 2021-11-22 00:50
 */
public class SelectVisitor extends MySqlASTVisitorAdapter {
    private final String EMPTY_TABLE_KEY = StrUtil.EMPTY;
    protected final ColumnInfo BASE_COLUMN_INFO =
            ColumnInfo.builder().columnName("is_delete").columnValue(new SQLBooleanExpr(Boolean.TRUE)).build();

    @Override
    public boolean visit(SQLBinaryOpExpr x) {
        // 在此阶段判断上一个节点是什么 并进行各种判断
        SQLObject parent = x.getParent();
        // on块
        if (parent instanceof SQLJoinTableSource) {
            // 拼接要加入的条件
            List<TableInfo> tableInfoList = getTableInfoList(((SQLJoinTableSource) parent),
                    new ArrayList<>());
            filterCustomColumnTable(x, tableInfoList);
            SQLExpr newExpr = getNewExpr(tableInfoList, x);
            ((SQLJoinTableSource) parent).setCondition(newExpr);
            return false;
        }
        // where块
        if (parent instanceof MySqlSelectQueryBlock) {
            // 获取表信息时忽略右表
            List<TableInfo> tableInfoList = getTableInfoList(((MySqlSelectQueryBlock) parent).getFrom(),
                    new ArrayList<>(), Boolean.FALSE);
            filterCustomColumnTable(x, tableInfoList);
            SQLExpr newExpr = getNewExpr(tableInfoList, x);
            ((MySqlSelectQueryBlock) parent).setWhere(newExpr);
            return false;
        }
        // where块
        if (parent instanceof MySqlDeleteStatement) {
            // 获取表信息时忽略右表
            List<TableInfo> tableInfoList = getTableInfoList(((MySqlDeleteStatement) parent).getTableSource(),
                    new ArrayList<>(), Boolean.FALSE);
            filterCustomColumnTable(x, tableInfoList);
            SQLExpr newExpr = getNewExpr(tableInfoList, x);
            ((MySqlDeleteStatement) parent).setWhere(newExpr);
            return false;
        }
        return false;
    }

    /**
     * 遍历并获得当前层级下的表别名
     *
     * @param tableSource   表信息 由form块获取
     * @param tableInfoList 用于迭代的表信息集合
     * @return 表信息集合
     */
    protected List<TableInfo> getTableInfoList(SQLTableSource tableSource, List<TableInfo> tableInfoList) {
        return getTableInfoList(tableSource, tableInfoList, Boolean.TRUE);
    }

    /**
     * 遍历并获得当前层级下的表别名
     *
     * @param tableSource   表信息 由form块获取
     * @param tableInfoList 用于迭代的表信息集合
     * @param isGetRight    关联查询是是否获取右表信息
     * @return 表信息集合
     */
    private List<TableInfo> getTableInfoList(SQLTableSource tableSource, List<TableInfo> tableInfoList,
                                             Boolean isGetRight) {
        if (tableSource instanceof SQLSubqueryTableSource) {
            tableInfoList.add(new TableInfo(EMPTY_TABLE_KEY, tableSource.getAlias()));
        }

        if (tableSource instanceof SQLJoinTableSource) {
            SQLJoinTableSource joinSource = (SQLJoinTableSource) tableSource;
            getTableInfoList(joinSource.getLeft(), tableInfoList, isGetRight);
            // 这里如果是join语句在where条件中是不需要加入右表的 因为关联查询关联表不应该影响数据条数 应该只影响关联结果
            if (isGetRight) {
                getTableInfoList(joinSource.getRight(), tableInfoList, true);
            }
        }

        if (tableSource instanceof SQLExprTableSource) {
            tableInfoList.add(new TableInfo(String.valueOf(tableSource), tableSource.getAlias()));
        }
        return tableInfoList;
    }

    /**
     * 过滤已经自定义了字段的表信息, 例如当前条件中已经追加了 is_delete is false 则不需要再在语句中加入 is_delete is true
     *
     * @param where         条件
     * @param tableInfoList 表信息
     */
    private void filterCustomColumnTable(SQLExpr where, List<TableInfo> tableInfoList) {
        if (Objects.isNull(where)) {
            return;
        }

        // 遍历左右两端
        SQLExpr sqlExpr = where;
        while (sqlExpr instanceof SQLBinaryOpExpr && Objects.nonNull(sqlExpr = ((SQLBinaryOpExpr) sqlExpr).getLeft())) {
            filterCustomColumnTable(sqlExpr, tableInfoList);
        }

        sqlExpr = where;
        while (sqlExpr instanceof SQLBinaryOpExpr && Objects.nonNull(sqlExpr = ((SQLBinaryOpExpr) sqlExpr).getRight())) {
            filterCustomColumnTable(sqlExpr, tableInfoList);
        }

        // 未设置别名 则直接判断是否当前字段
        if (where instanceof SQLInSubQueryExpr) {
            filterCustomColumnTable(((SQLInSubQueryExpr) where).getExpr(), tableInfoList);
        }
        // 处理形如 A = 1 存在字段名相同的条件则去掉
        if (where instanceof SQLIdentifierExpr) {
            tableInfoList.removeIf(item -> Objects.isNull(item.getAlias()) && Objects.equals(BASE_COLUMN_INFO.getColumnName(),
                    ((SQLIdentifierExpr) where).getName()));
            return;
        }

        // 处理形如 a.A = 1 存在别名和字段名都相同的条件则去掉
        if (where instanceof SQLPropertyExpr) {
            SQLPropertyExpr propertyExpr = (SQLPropertyExpr) where;
            tableInfoList.removeIf(item -> StringUtils.equalsIgnoreCase(propertyExpr.getOwner().toString(),
                    StrUtil.blankToDefault(item.getAlias(), item.getTableName())) && StringUtils.equalsIgnoreCase(propertyExpr.getName(),
                    BASE_COLUMN_INFO.getColumnName()));
        }

    }

    /**
     * 获取新的条件语句（若需要进行配置之类的处理，在这里统一处理即可）
     *
     * @param tableInfoList 表信息
     * @param x             原始表达式
     * @return 处理后的表达式
     */
    private SQLExpr getNewExpr(List<TableInfo> tableInfoList, SQLBinaryOpExpr x) {
        SQLExpr allOpExpr = x;
        for (TableInfo item : tableInfoList) {
            SQLBinaryOpExpr addOpExpr = new SQLBinaryOpExpr();
            // 如果别名为空则将表名做别名处理
            addOpExpr.setLeft(new SQLPropertyExpr(StrUtil.blankToDefault(item.getAlias(), item.getTableName()),
                    BASE_COLUMN_INFO.getColumnName()));
            addOpExpr.setOperator(SQLBinaryOperator.Is);
            addOpExpr.setRight(BASE_COLUMN_INFO.getColumnValue());
            allOpExpr = SQLBinaryOpExpr.and(allOpExpr, addOpExpr);
        }
        return allOpExpr;
    }
}
